-module(agent_report).

-behaviour(gen_server).

%% API
-export([start_link/0]).
-export([setup/0]).
-export([report/0]).
-export([regist/1]).
-export([deploy/1]).
-export([undeploy/1]).
-export([test_query/0]).


%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).
-include("agent.hrl").
-record(state, {
                 %%container_pid, 	%% container pid in xengine
		 container ,    %%  container info
		 app_name_2_app   ,      %%  the deployed app list , name -> app
		 domain_2_app , %%  the domain to  app
		 monitor_log_file,	%% monitor log file 
		 cpu       ,           %% 上一周期container cpu负荷
		 pid
		}).
test_query()->
	gen_server:call(agent_report,{test_query}).


report()->
	gen_server:cast(agent_report,{report}).

regist(Any)->
	gen_server:call(?MODULE,{regist,Any}).

deploy(App)->
	gen_server:call(?MODULE,{deploy,App}).
	
undeploy(all)->
        gen_server:call(?MODULE,{undeploy,all});
undeploy(App)->
	gen_server:call(?MODULE,{undeploy,App}).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
%% Description: Starts the server
%%--------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
        error_logger:info_msg("starting agent_report .....~n"),
        %%todo最好是在depoy第一个应用的时候注册定时器
        case     timer:apply_interval(5000, ?MODULE , report, []) of
                {ok , _ } ->
                            mnesia_op:insert(#agent_state{cpu=0}),
			    {ok, #state{
			        app_name_2_app=ets:new(?MODULE,[ordered_set,private]),
       			        domain_2_app=ets:new(?MODULE,[ordered_set,private]),
			        monitor_log_file=get_monitor_log(),
			        cpu =0 			     
                        	}};
                 {error , Reason } ->
                                {stop ,Reason } 
         end.

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({test_query},_From,State)->
	Reply={ 
%%		{container_pid, State#state.container_pid} ,
		{container, State#state.container } ,
		{app_name_2_app , ets:tab2list(State#state.app_name_2_app) } ,
		{domain_2_app , ets:tab2list(State#state.domain_2_app) } ,
		{monitor_log_file, State#state.monitor_log_file } 
		},
	{reply , Reply ,  State } ;
handle_call({undeploy,all},_From,State)->	
        mnesia_op:clear_table(app),
        { reply , ok ,  State#state{  app_name_2_app=ets:new(?MODULE,[ordered_set,private]),
       			        domain_2_app=ets:new(?MODULE,[ordered_set,private]) }  }   ;    
        
handle_call({undeploy,AppName},_From,State)->
        case ets:lookup(State#state.app_name_2_app, AppName)  of
                [] ->
                        { reply , { error , "no suche a app named :" ++ AppName}  , State } ;
                [{_ , App}] ->
			mnesia_op:delete(App),
			ets:delete(State#state.app_name_2_app, AppName ),
                        ets:delete(State#state.domain_2_app, App#app.domain ),
                       { reply ,ok  , State } 
        end;

handle_call({deploy,App},_From,State)->
   %%     error_logger:info_msg("agent_report:recving a deploy message , app=~p~n",[App]),
	mnesia_op:insert(#app{name=App,domain=App}),
	ets:insert(State#state.app_name_2_app, {App#app.name, App} ),
	ets:insert(State#state.domain_2_app, {App#app.domain, App} ),
	{reply , ok ,  State } ;
handle_call( {regist, {Container,Pid} } , _From , State ) ->
	mnesia_op:insert(#agent_state{pid=Pid, container=Container}),
	{reply , ok , State#state{container=Container , pid=Pid     }} ;
handle_call(_Request, _From, State) ->
  	  Reply = ok,
   	 {reply, Reply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({report} , State )->
	%%%--从文件中读入应用符合情况并汇总----------------------
	%%% 文件格式为
	%%  应用名            cpu      mem        io      connection
	%%  app1        10        0         0          0
	%%  app2        10        0         0          0
	%%  app3        10        0         0          0
	%%  app4        10        0         0          0
	%%%-------------------------------------------------
%%	 error_logger:info_msg("begin report!~n"),
	 case ets:info(State#state.app_name_2_app,size) of
	        0 ->
	                { noreply , State } ;
	        _ ->
                	 AppRuntimes=parse_instance_runtimes(State),
                	 ContainerRuntime=parse_container_runtime(State),
                	 send_report( State#state.pid,
                	                        State#state.container,AppRuntimes,
                	                         ContainerRuntime ,
                	                         State#state.app_name_2_app),
                         Runtime=ContainerRuntime#container_runtime.runtime,             	                         
                	 {
                	        noreply,
                	        State#state{cpu=Runtime#runtime.cpu}
                	  } 
         end;
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
parse_container_runtime(State)->
        Container=State#state.container,
	Result = trim_right(os:cmd( io_lib:format("sh script/monitor_container.sh ~s",[Container#container.home]) )),
        {Cpu,_}=string:to_integer(Result),
        #container_runtime{name=Container#container.name,runtime=#runtime{cpu=(Cpu*2+State#state.cpu)/3}}.

%%parse_container_runtime_debug(State)->
%%        Container=State#state.container,
%%        #container_runtime{name=Container#container.name,
%%                                pid=undefined,
%%                                runtime=#runtime{cpu=(60*2+State#state.cpu)/3}}.

parse_instance_runtimes(State ) ->
%%        #state{container=Container,app_name_2_app=AppName2App } = State,
        File=State#state.monitor_log_file,
%%	error_logger:info_msg(io_lib:format("open the log file , named:~s~n",[File])),
	case  file:open(File,read) of
		{ok ,S } ->
			AppRuntimes=parse_instance_runtimes_1(S,[],State),
			file:close(S),
			AppRuntimes;
		{error , Why } ->
			error_logger:info_msg("open the log file:~p error :~p ", [File,Why] ),
			{error, Why }
	end.
parse_instance_runtimes_1(S,InstanceRuntimes,State)->

        #state {  container=Container    , 
                  domain_2_app=Domain2App }=State,

	case io:get_line(S," \n") of
		eof ->
			InstanceRuntimes ;
		Line ->
           %%             error_logger:info_msg("get a line:"++Line),
                        
                        case  string:substr(Line,1,1) of
                           "#"  -> 
                                parse_instance_runtimes_1(S,  InstanceRuntimes, State );
                           _ ->
                                case io_lib:fread("~s\t~f\t~d\t~d\t~d\n",Line) of
				   {ok , [Domain , Cpu, Mem, Up, Down],[] } -> 
				        case  ets:lookup(Domain2App,Domain) of
				                [] ->
				                        parse_instance_runtimes_1(S,InstanceRuntimes, State );
				                [{_ , App}]  ->
				                        InstanceRuntime=#instance_runtime{
                                                                        instance=#instance  {
                                                                              container_name=Container#container.name , 
					                                      app_name=App#app.name } , 
	                						runtime=#runtime{
	                						      cpu=Cpu/100,mem=Mem,up=Up,down=Down }},
%%                                                       error_logger:info_msg("get a instanceruntime:~p~n",[InstanceRuntime]), 	                						      
				                       parse_instance_runtimes_1(S, 
				                                                 [InstanceRuntime|InstanceRuntimes],
				                                                  State)   
				        end;
				{more, _RestFormat, _Nchars, _InputStack} ->
				%%	error_logger:info_msg(io_lib:format("parse the line ~p error",[Line])),
					parse_instance_runtimes_1(S,  InstanceRuntimes , State );
				{error, What}  ->
					error_logger:info_msg(io_lib:format("file format error: What=~p,Line=~p~n",[What,Line])),
					parse_instance_runtimes_1(S,  InstanceRuntimes, State )
			        end

                           
                           
                        end

			


	end.
	

send_report(Pid , Container ,  InstanceRuntimes , ContainerRuntime , AppName2App )->
%%	error_logger:info_msg("InstanceRuntimes is ~p~n",[InstanceRuntimes]),
%%	error_logger:info_msg("AppName2App is ~p~n",[ets:tab2list(AppName2App)]),
	F = fun (InstanceRuntime) ->
		   ets:member(AppName2App, (InstanceRuntime#instance_runtime.instance)#instance.app_name) 
	end,
	
	IRs=lists:filter(F,InstanceRuntimes),
%%	error_logger:info_msg("filtered InstanceRuntimes is ~p~n",[IRs]),

	gen_server:cast( Pid,  { report, { IRs, ContainerRuntime}}).

get_monitor_log()->
	case os:getenv("monitor.log") of false -> ?DEFAULT_MONITOR_LOG;	Any ->  Any end.


setup()->
	agent_report:regist(self(), #container{name="container1",ip="localhost"}),
	agent_report:deploy( #app{name="app1",domain="",source="",target=""}),
	agent_report:report().
	
trim_right(Str)->
        case string:str(Str,"\n") of
                0 ->
                        Str;
                1 ->
                        "0"; 
                Index ->
                        string:substr(Str,1,Index-1)
        end.
	

